#include "RawInput.h"

#include <stdio.h>
#include <cassert>
#include <algorithm>
#include <vector>



namespace TLAC::Input
{
	bool RawInput::PollInput() {
		hid_controller_handler_.BeginTickMessages();

		// All messages are broadcast to all handlers. Each handler should filter & ignore as required
		WindowMessageData device_change_message;
		while (device_change_message_queue_.try_dequeue(device_change_message)) {
			const HANDLE device_handle = reinterpret_cast<HANDLE>(device_change_message.lParam);
			if (device_change_message.wParam == GIDC_ARRIVAL) {
				RID_DEVICE_INFO device_info;
				device_info.cbSize = sizeof(device_info);
				UINT data_size{ sizeof(device_info) };
				if (GetRawInputDeviceInfo(device_handle, RIDI_DEVICEINFO, &device_info, &data_size) == static_cast<UINT>(-1)) {
					printf("[TLAC] RawInput::PollInput(): GetRawInputDeviceInfo() Error: %d\n", GetLastError());
					continue;
				}
				hid_controller_handler_.HandleDeviceArrival(device_handle, device_info);
			}
			else {
				hid_controller_handler_.HandleDeviceRemoval(device_handle);
			}
		}

		RawInputDetails::unique_malloc_ptr<RAWINPUT> input_message;
		while (input_message_queue_.try_dequeue(input_message)) {
			hid_controller_handler_.HandleInput(*input_message);
		}

		hid_controller_handler_.EndTickMessages();
		return true;
	}

	const std::vector<std::array<std::uint32_t, 32>>* RawInput::GetSliderUpdates() const
	{
		if (hid_controller_handler_.GetInputsAvailable().has_any_value) {
			return &(hid_controller_handler_.GetSliderUpdates());
		}
		return nullptr;
	}

	bool RawInput::RegisterWMInput(HWND hwnd)
	{
		std::vector<RAWINPUTDEVICE> devices;
		hid_controller_handler_.AddDevicesToRegister(devices);
		for (auto& device : devices) {
			device.dwFlags |= RIDEV_INPUTSINK;
			device.hwndTarget = hwnd;
		}
		if (!RegisterRawInputDevices(devices.data(), devices.size(), sizeof(RAWINPUTDEVICE))) {
			printf("[TLAC] RawInput::RegisterWMInput(): RegisterRawInputDevices() Error: %d\n", GetLastError());
			return false;
		}
		// GetCurrentDevices();
		return true;
	}

	/*bool RawInput::UnregisterWMInput()
	{
		std::vector<RAWINPUTDEVICE> devices;
		devices.push_back({ CustomHidControllerHandler::TLC_USAGE_PAGE, CustomHidControllerHandler::TLC_USAGE, RIDEV_REMOVE, NULL });
		if (!RegisterRawInputDevices(devices.data(), devices.size(), sizeof(RAWINPUTDEVICE))) {
			printf("[TLAC] RawInput::UnregisterWMInput(): RegisterRawInputDevices() Error: %d\n", GetLastError());
			return false;
		}
		return true;
	}*/

	/*void RawInput::GetCurrentDevices()
	{
		std::vector<RAWINPUTDEVICELIST> devices(1);
		UINT devices_capacity{ 1 };
		UINT num_devices_returned;
		while (num_devices_returned = GetRawInputDeviceList(devices.data(), &devices_capacity, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
			auto error = GetLastError();
			if (error == ERROR_INSUFFICIENT_BUFFER) {
				devices.resize(devices_capacity);
				continue;
			}
			else {
				printf("[TLAC] RawInput::GetCurrentDevices(): GetRawInputDeviceList() Error: %d\n", error);
				return;
			}
		}
		devices.resize(num_devices_returned);
		printf("[TLAC] RawInput::GetCurrentDevices(): GetRawInputDeviceList() returned %d devices\n", num_devices_returned);
	}*/

	LPARAM RawInput::HandleInputMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		{
			auto call_defwndproc_on_exit = wil::scope_exit([&] {
				/* The documentations only mention calling DefWindowProc if
				* GET_RAWINPUT_CODE_WPARAM(wParam) == RIM_INPUT. However this sounds pretty weird,
				* so I'm guessing that it doesn't hurt to call DefWindowProc in all cases anyways.
				* https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-input */
				DefWindowProc(hWnd, message, wParam, lParam);
				});

			UINT data_size{ 0 };
			if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &data_size, sizeof(RAWINPUTHEADER)) == static_cast<UINT>(-1)) {
				printf("[TLAC] RawInput::HandleInputMessage(): GetRawInputData() getting data size failed: %d\n", GetLastError());
				return 0;
			}
			RawInputDetails::unique_malloc_ptr<RAWINPUT> input_data((PRAWINPUT)std::malloc(data_size));
			if (!input_data) {
				printf("[TLAC] RawInput::HandleInputMessage(): memory allocation failed\n");
				return 0;
			}
			if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, input_data.get(), &data_size, sizeof(RAWINPUTHEADER)) == static_cast<UINT>(-1)) {
				printf("[TLAC] RawInput::HandleInputMessage(): GetRawInputData() getting data failed\n");
				return 0;
			}
			input_message_queue_.emplace(std::move(input_data));
		}

		// TODO: maybe use GetRawInputBuffer() here to retrieve all outstanding WM_INPUT messages

		return 0;
	}

	LPARAM RawInput::HandleInputDeviceChangeMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		device_change_message_queue_.enqueue({ wParam = wParam, lParam = lParam });
		return 0;
	}

}